﻿using UnityEngine;
using System;
using System.Collections;
using System.Net.Sockets;
using System.Net;
using UnityEngine.UI;
using System.Threading;
using System.Text;

namespace Connections {

	public class ServerManager : MonoBehaviour {

		public bool minigameStart;

		private Socket m_listener;
		private bool m_startedAccept;
		private static SyncObject m_response;
		private bool m_serverStarted;
		internal static bool m_deviceConnected;
		private String ipAddress;
		private String deviceName;

		//delegate
		private delegate void RespondDelegate(string x, string y);
		private RespondDelegate rdel;

		// commands
		private ICommand nameCommand;
		private ICommand hackCommand;
		private ICommand textureCommand;

		private static ServerManager instance = null;

		void Awake () {
			Application.runInBackground = true;
			if (instance == null) {
				DontDestroyOnLoad(this.gameObject);
				instance = this;
				Debug.Log ("Initialized SM");
				try {
					IPAddress[] addrs = Dns.GetHostEntry (Dns.GetHostName ()).AddressList;
					if (addrs.Length < 1) {
						ipAddress = "No address could be found!";
						// throw new System.Exception("No address could be found");
						return;
					}
					foreach (IPAddress addr in addrs) {
						Debug.Log (Dns.GetHostName () + " - " + addr);
					}
					ipAddress = "Your IP: " + addrs [0];
				} catch (Exception e) {
					Debug.Log (e.ToString ());
				}
			} else if (instance != this) {
				Destroy (this.gameObject);
			}
		}

		void Start() {
			Network.InitializeServer(2, 25003);
			StartListening ();
			ServerManager.m_response = new SyncObject();
			ServerManager.m_response.deviceConnected = false;
			m_response.state.received = true;
			m_startedAccept = false;
			deviceName = "";
			minigameStart = false;

			// setup commands
			nameCommand = new NameCommand ();
			hackCommand = new HackCommand ();
			textureCommand = new TextureCommand();

			// add to delegate
			rdel += nameCommand.respond;
			rdel += hackCommand.respond;
			//rdel += textureCommand.respond;
		}

		void Update() {
			if (m_serverStarted) {
				if (!m_startedAccept) {
					m_startedAccept = true;
					Debug.Log ("waiting for connection...");
					ServerManager.m_response.listener = m_listener;
					m_listener.BeginAccept (new AsyncCallback (AcceptCallback), m_response);
				}

				if(ServerManager.m_response.deviceConnected && ServerManager.m_response.state.received) {
					Debug.Log(ServerManager.m_response.state.sb.ToString());
					String[] parts = ServerManager.m_response.state.sb.ToString().Split(':');
					if(parts.Length == 2) {
						rdel(parts[0], parts[1]);
					}
					ServerManager.Receive (m_response.state.workSocket);
				} else if(ServerManager.m_response.deviceConnected) {
					// commands
					if(nameCommand.needsToRespond()) {
						ServerManager.Send (nameCommand.getCommand());
					}
					if(hackCommand.needsToRespond()) {
						ServerManager.Send (hackCommand.getCommand());
					}
					/*if(textureCommand.needsToRespond()) {
						ServerManager.Send (textureCommand.getCommand());
					}*/
				}
			}
		}

		public static ServerManager getInstance() {
			return instance;
		}

		public String getIPAddress() {
			return ipAddress;
		}

		public void setDeviceName(String name) {
			deviceName = name;
		}

		public String getDeviceName() {
			return deviceName;
		}

		public static void Send(string data) {
			if (m_response.deviceConnected) {
				Send (m_response.state.workSocket, data);
			}
		}

		public static void Receive(Socket handler) {
			try {
				m_response.state = new StateObject ();
				m_response.state.workSocket = handler;
				handler.BeginReceive (m_response.state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback (ReadCallback), m_response);
			} catch(Exception e) {
				Debug.Log(e.ToString());
			}
		}

		public void StartListening() {
			if (m_serverStarted)
				return; // Server is already running

			IPHostEntry ipHostInfo = Dns.GetHostEntry (Dns.GetHostName ());
			IPAddress addr = ipHostInfo.AddressList [0];
			IPEndPoint ipEndPoint = new IPEndPoint (addr, 25002);

			m_listener = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

			try {
				m_listener.Bind(ipEndPoint);
				m_listener.Listen(100);
			} catch(Exception e) {
				Debug.Log(e.ToString());
			}

			m_serverStarted = true;
		}

		public void StopListening() {
			if (!m_serverStarted)
				return; // Server is already not running
			try {
				ServerManager.m_response.state.workSocket.Shutdown (SocketShutdown.Both);
				ServerManager.m_response.state.workSocket.Close ();
				m_listener.Shutdown (SocketShutdown.Both);
				m_listener.Close ();
				m_listener = null;
			} catch (Exception e) {
				Debug.Log(e.ToString());
			}
		}

		public static void AcceptCallback(IAsyncResult ar) {
			SyncObject sync = (SyncObject)ar.AsyncState;
			Socket handler = sync.listener.EndAccept(ar);
			sync.state.workSocket = handler;
			sync.deviceConnected = true;
			Debug.Log ("Device Connected - " + handler.RemoteEndPoint.ToString());
			Send (handler, "name:?");
		}

		public static void ReadCallback(IAsyncResult ar) {
			String content = String.Empty;

			SyncObject sync = (SyncObject)ar.AsyncState;
			Socket handler = sync.state.workSocket;

			int bytesRead = handler.EndReceive (ar);

			if (bytesRead > 0) {
				sync.state.sb.Append(Encoding.ASCII.GetString(sync.state.buffer, 0, bytesRead));

				content = sync.state.sb.ToString();
				if(content.IndexOf("\r\n") > -1) {
					Debug.Log ("Read " + content.Length + " bytes from socket. \nData: " + content);
					sync.state.received = true;
				} else {
					handler.BeginReceive(sync.state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), sync);
				}
			}
		}

		private static void Send(Socket handler, String data) {
			data += "\r\n";
			byte[] encData = Encoding.ASCII.GetBytes (data);
			handler.BeginSend (encData, 0, encData.Length, 0, new AsyncCallback (SendCallback), handler);
		}

		public static void SendCallback(IAsyncResult ar) {
			try {
				Socket handler = (Socket)ar.AsyncState;
				int bytesSent = handler.EndSend(ar);
				Debug.Log("Sent " + bytesSent + " bytes to client");
			} catch(Exception e) {
				Debug.Log(e.ToString());
			}
		}

		void OnDestroy() {
			try {
				StopListening ();
			} catch(SocketException se) {
				Debug.Log(se.ToString());
			}
		}
	}

	public class SyncObject {
		public Socket listener = null;
		public bool deviceConnected {
			get {return ServerManager.m_deviceConnected;}
			set {ServerManager.m_deviceConnected = value;}
		}
		public StateObject state = new StateObject();
	}

	public class StateObject {
		public Socket workSocket = null;
		public const int BufferSize = 1024;
		public byte[] buffer = new byte[BufferSize];
		public StringBuilder sb = new StringBuilder();
		public bool received = false;
	}
}
